{------------------------------------------------------------------------------
#
#	Apple Macintosh Developer Technical Support
#
#	AppleTalk GetZoneList Sample Application
#
#	GetZoneList
#
#	GetZoneList.p	-	Pascal Source
#
#	Copyright © 1988 Apple Computer, Inc.
#	All rights reserved.
#
#	Versions:	1.0					11/88
#
#	Components:	GetZoneList.p		November 1, 1988
#				GetZoneList.r		November 1, 1988
#				GetZoneList.make	November 1, 1988
#
#	GetZoneList is a sample application that uses
#	AppleTalk ATP and ZIP to obtain a list of zones
#	on an AppleTalk internet.
#
#	GetZoneList also demonstrates using a signal, or
#	failure-catching mechanism to recover from error
#	situations.
#
#	GetZoneList is based on MACDTS Sample.p. For more
#	description and explanantion of the non-example
#	specific areas of this application, please refer to
#	either Sample.p or TESample.p.
#
------------------------------------------------------------------------------}

PROGRAM GetZoneList;

USES
	MemTypes, QuickDraw, OSIntf, ToolIntf, AppleTalk, PackIntf, FixMath, Script, UFailure;

CONST
	_WaitNextEvent			= $A860;
	_Unimplemented			= $A89F;
	kSysEnvironsVersion		= 1;
	kOSEvent				= app4Evt;	{event used by MultiFinder}
	kSuspendResumeMessage	= 1;		{high byte of suspend/resume event message}
	kResumeMask				= 1;		{bit of message field for resume vs. suspend}
	
	kCR				= 13;				{carriage return character}
	kENTER			= 3;				{enter character}
	kScrollBarWidth	= 15;				{the width of the scrollbar in the list}
	kListInset		= -1;				{adjustment for list frame}
	kATPTimeOutVal	= 3;				{re-try ATP SendRequest every 3 seconds}
	kATPRetryCount	= 5;				{for five times}
	kZonesSize		= 578;				{size of buffer for zone names}
	kGZLCall		= $08000000;		{GetZoneList indicator}
	kZIPSocket		= 6;				{the Zone Information Protocol socket}
	kMoreZones		= $FF000000;		{mask to see if more zones to come}
	kZoneCount		= $0000FFFF;		{mask to count zones in buffer}
	kHilite			= 1;				{hilite value for button control}
	kDeHilite		= 0;				{dehilite value for button control}
	kHiliteDelay	= 5;				{time in ticks to leave button hilited}
	
	kMinHeap		= 0;
	kMinSpace		= 0;
	
	sErrStrings		= 128;				{error string STR#}
	eStandardErr	= 1;
	eWrongMachine	= 2;
	eSmallSize		= 3;
	eNoMemory		= 4;
	eAppleTalk		= 5;
	eNoZones		= 6;
	
	rAboutAlert		= 128;				{about alert}
	rZoneDialog		= 129;				{zone list dialog}
	dZoneList		= 2;				{user item that is zone list}
	dDefault		= 3;				{user item that is default indicator}
	rUserAlert		= 130;				{error alert}

	rMenuBar		= 128;				{application's menu bar}

	mApple			= 128;				{Apple menu}
	iAbout			= 1;

	mFile			= 129;				{File menu}
	iNew			= 1;
	iClose			= 4;
	iQuit			= 12;

	mEdit			= 130;				{Edit menu}
	iUndo			= 1;
	iCut			= 3;
	iCopy			= 4;
	iPaste			= 5;
	iClear			= 6;
	
	{1.01 - kDITop and kDILeft are used to locate the Disk Initialization dialogs.}
	kDITop		= $0050;
	kDILeft		= $0070;


VAR
	gMac				: SysEnvRec;	{set up by Initialize}
	gHasWaitNextEvent	: BOOLEAN;		{set up by Initialize}
	gInBackground		: BOOLEAN;		{maintained by Initialize and DoEvent}
	
	gList				: ListHandle;	{the list to be filled with zone names}

{$S Initialize}
FUNCTION TrapAvailable(tNumber: INTEGER; tType: TrapType): BOOLEAN;
BEGIN
	TrapAvailable := NGetTrapAddress(tNumber, tType) <> GetTrapAddress(_Unimplemented);
END; {TrapAvailable}


{$S Main}
PROCEDURE FailOSErrMsg(result, message: INTEGER);
BEGIN
	IF result <> noErr THEN
		Failure(result, message);
END; {SignalOSErrMsg}


{$S Main}
PROCEDURE FailNILMsg(p: UNIV Ptr; message: INTEGER);
BEGIN
	IF p = NIL THEN
		Failure(memFullErr, message);
END; {FailNILMsg}


{$S Main}
PROCEDURE AlertUser(error: INTEGER; message: LongInt);

{Display an alert to inform the user of an error. Message acts as an 
 index into a STR# resource of error messages. If no message is given,
 i.e. = 0, then use a standard message. If error is not noErr then
 display it as well.}

VAR
	msg1, msg2	: Str255;
	itemHit		: INTEGER;
BEGIN
	IF message = 0 THEN message := eStandardErr;
	GetIndString(msg1, sErrStrings, message);
	IF error = noErr THEN
		msg2 := ''
	ELSE
		NumToString(error, msg2);
	ParamText(msg1, msg2, '', '');
	itemHit := Alert(rUserAlert, NIL);
END; {AlertUser}


{$S Main}
FUNCTION IsDAWindow(window: WindowPtr): BOOLEAN;
BEGIN
	IF window = NIL THEN
		IsDAWindow := FALSE
	ELSE	{DA windows have negative windowKinds}
		IsDAWindow := WindowPeek(window)^.windowKind < 0;
END; {IsDAWindow}


{$S Main}
FUNCTION IsAppWindow(window: WindowPtr): BOOLEAN;
BEGIN
	IF window = NIL THEN
		IsAppWindow := FALSE
	ELSE	{application windows have windowKinds >= userKind (8) or dialogKind (2)}
		WITH WindowPeek(window)^ DO
			IsAppWindow := (windowKind >= userKind) | (windowKind = dialogKind);
END; {IsAppWindow}


{$S Main}
PROCEDURE BuildZoneList;

{Create the list of zones on the network. Find a bridge to talk to , if one is
 present, then ask it for zone names. Add the names to the list in the dialog.}

VAR
	dATPPBptr					: ATPPBptr;		{the parameter block for GetZoneList call}
	dBDS						: BDSElement;	{the BDS for GetZoneList call}
	dZones, dCurr				: Ptr;			{the data buffer for GetZoneList call}
	dIndex, dCount, dNode, dNet	: INTEGER;
	ignore						: INTEGER;
	cSize						: Point;
	fi							: FailInfo;
	
	PROCEDURE CleanUp;
	BEGIN
		IF dATPPBptr <> NIL THEN
			DisposPtr(Ptr(dATPPBptr));					{get rid of pb block}
		IF dZones <> NIL THEN
			DisposPtr(dZones);							{and buffer}
	END; {CleanUp}
	
	PROCEDURE HandleErr(error: INTEGER; message: LongInt);
	BEGIN
		CleanUp;										{get rid of allocated junk}
	END;
	
BEGIN
	dATPPBptr := NIL;									{init some important variables}
	dZones := NIL;
	CatchFailures(fi, HandleErr);
	
	dATPPBptr := ATPPBptr(NewPtr(SIZEOF(ATPParamBlock)));
	FailNILMsg(dATPPBptr, eNoMemory);
	dZones := NewPtr(kZonesSize);
	FailNILMsg(dZones, eNoMemory);
	WITH dBDS DO BEGIN									{set up BDS}
		buffSize := kZonesSize;
		buffPtr := dZones;
		END;
	WITH dATPPBptr^ DO BEGIN							{set up pb block}
		atpFlags := 0;
		FailOSErrMsg(GetNodeAddress(ignore,
			addrBlock.aNet), eAppleTalk);				{get net of bridge}
		IF addrBlock.aNet = 0 THEN
			Failure(0, eNoZones);						{bail if no zones present}
		addrBlock.aNode := GetBridgeAddress;			{get node of bridge}
		addrBlock.aSocket := kZIPSocket;				{the socket we want}
		reqLength := 0;
		reqPointer := NIL;
		bdsPointer := @dBDS;
		numOfBuffs := 1;
		timeOutVal := kATPTimeOutVal;
		retryCount := kATPRetryCount;
		END;
	dIndex := 1;
	dCount := 0;
	SetPt(cSize, 0, 0);									{we always stuff into first}
	REPEAT
		dATPPBptr^.userData := kGZLCall + dIndex;		{indicate GetZoneList request}
		FailOSErrMsg(PSendRequest(dATPPBptr,
			FALSE), eAppleTalk);						{send sync request}
		dCount := dCount + BAND(dBDS.userBytes,
					kZoneCount);						{find out how many returned}
		dCurr := dZones;								{put current pointer at start}
		REPEAT											{get each zone}
			ignore := LAddRow(1, 0, gList);				{create new cell at start}
			LSetCell(POINTER(ORD4(dCurr) + 1), dCurr^,
						cSize, gList);					{stuff in zone}
			dCurr := POINTER(ORD4(dCurr) + dCurr^+1);	{bump up current pointer}
			dIndex := dIndex + 1;						{increment which zone}
		UNTIL dIndex > dCount;
	UNTIL (BAND(dBDS.userBytes, kMoreZones) <> 0);		{keep going until none left}
	CleanUp;
	
	Success(fi);
END; {BuildZoneList}


{$S Main}
PROCEDURE ZoneListDraw(dlg: DialogPtr; item: INTEGER);

{The user item procedure for the zone list user item and default
 box user item in the dialog. Draw the list and the frame that goes with it.
 Draw the default box around the OK button.}

VAR
	port	: GrafPtr;
	kind	: INTEGER;
	h		: Handle;
	r		: Rect;
	ps		: PenState;
	
BEGIN
	GetPort(port);									{save old port}
	SetPort(dlg);									{make dialog port}
	CASE item OF
	dZoneList: BEGIN
			LUpdate(dlg^.visRgn, gList);			{re-draw list}
			GetDItem(dlg, dZoneList, kind, h, r);
			InsetRect(r, kListInset, kListInset);
			FrameRect(r);							{re-draw frame}
		END;
	dDefault: BEGIN
			GetDItem(dlg, dDefault, kind, h, r);
			GetPenState(ps);
			PenSize(3, 3);
			InsetRect(r, -4, -4);
			FrameRoundRect(r, 16, 16);				{draw default box}
			SetPenState(ps);
		END;
	END;
	SetPort(port);									{restore old port}
END; {ZoneListDraw}


{$S Main}
FUNCTION ListFilter (dlg: DialogPtr; VAR event: EventRecord;
											VAR item: INTEGER) : BOOLEAN;

{Passed as parameter to ModalDialog. Handle key presses and mouse clicks
 from the user. Do all the right default actions since we override them
 by virtue of our existence.}

VAR
	port		: GrafPtr;
	loc			: Point;
	kind		: INTEGER;
	h			: Handle;
	r			: Rect;
	ignore		: BOOLEAN;
	key			: SignedByte;
	finalTicks	: LongInt;
BEGIN
	ListFilter := FALSE;								{always default FALSE}
	CASE event.what OF
		keyDown, autoKey: BEGIN							{check for <cr> or <enter>}
			key := SignedByte(event.message);
			IF key IN [kCR, kENTER] THEN BEGIN			{it was a <cr> or <enter>}
				GetDItem(dlg, ok, kind, h, r);
				HiliteControl(ControlHandle(h), kHilite);
				Delay(kHiliteDelay, finalTicks);
				HiliteControl(ControlHandle(h), kDeHilite);
				ListFilter := TRUE;						{so we handle it}
				item := 1;								{and make the first item hit}
			END;
		END;
		mouseDown: BEGIN								{we want mouseDowns}
			GetPort(port);
			SetPort(dlg);
			loc := event.where;
			GlobalToLocal(loc);							{find where clicked}
			GetDItem(dlg, dZoneList, kind, h, r);		{get rect for list}
			IF PtInRect(loc, r) THEN BEGIN				{if clicked insideÉ}
				ListFilter := TRUE;						{we take care of it}
				ignore := LClick(loc, event.modifiers, 
									gList);				{by passing click to list}
			END;
			SetPort(port);
		END;
	END;
END; {ListFilter}


{$S Main}
PROCEDURE DoZoneList;

{Put up a modal dialog that shows a list of the zones on the net. Create the dialog
 and list, call BuildZoneList to fill it, then wait for the user to click OK.}

VAR
	dlg						: DialogPtr;
	item, kind				: INTEGER;
	h						: Handle;
	r, rView, dataBounds	: Rect;
	cSize					: Point;
	fi						: FailInfo;
	hor, ver				: INTEGER;
	
	PROCEDURE CleanUp;
	BEGIN
		IF gList <> NIL THEN
			LDispose(gList);								{get rid of list}
		IF dlg <> NIL THEN
			DisposDialog(dlg);								{get rid of dialog}
	END; {CleanUp}

	PROCEDURE HandleErr(error: INTEGER; message: LongInt);
	BEGIN
		CleanUp;											{release junk}
	END;
	
BEGIN
	gList := NIL;											{init some important variables}
	dlg := NIL;
	CatchFailures(fi, HandleErr);
	
	dlg := GetNewDialog(rZoneDialog, NIL, POINTER(-1));		{create dialog}
	FailNILMsg(dlg, eNoMemory);
	
	{We center the dialog horizontally and position it vertically one-third the
	 distance from the menu bar to the bottom of the main device. We do not
	 check for the dialog extending past the bottom of the device because we
	 know the dialog is not that big. You may wish to make that check.}
	WITH dlg^.portRect DO
		hor := right - left;
	WITH screenBits.bounds DO BEGIN
		hor := ((right - left) - hor) DIV 2;
		ver := ((bottom - top) - GetMBarHeight) DIV 3;
	END;
	MoveWindow(dlg, hor, ver, FALSE);
	
	GetDItem(dlg, dDefault, kind, h, r);
	h := @ZoneListDraw;										{connect drawing procedure}
	SetDItem(dlg, dDefault, kind, h, r);
	GetDItem(dlg, dZoneList, kind, h, r);
	h := @ZoneListDraw;										{connect drawing procedure}
	SetDItem(dlg, dZoneList, kind, h, r);
	rView := r;
	WITH rView DO
		right := right - kScrollBarWidth;					{adjust rectangle for scroll}
	SetRect(dataBounds, 0, 0, 1, 0);						{init to one-wide list}
	SetPt(cSize, 0, 0);
	gList := LNew(rView, dataBounds, cSize, 0, WindowPtr(dlg),
					FALSE, FALSE, FALSE, TRUE);				{create with vertical scroll}
	FailNILMsg(gList, eNoMemory);
	BuildZoneList;											{put the stuff into the list}
	SetPt(cSize, 0, 0);
	LSetSelect(TRUE, cSize, gList);							{select the first guy}
	LDoDraw(TRUE, gList);									{turn on the list}
	ShowWindow(dlg);										{turn on the dialog}
	REPEAT
		ModalDialog(@ListFilter, item);						{accept events}
	UNTIL item = ok;										{until he presses 'ok'}
	CleanUp;
	
	Success(fi);
END; {DoZoneList}


{$S Main}
FUNCTION DoCloseWindow(window: WindowPtr) : BOOLEAN;
BEGIN
	DoCloseWindow := TRUE;
	IF IsDAWindow(window) THEN
		CloseDeskAcc(WindowPeek(window)^.windowKind);
	IF IsAppWindow(window) THEN
		CloseWindow(window);
END; {DoCloseWindow}


{$S Initialize}
PROCEDURE Initialize;
VAR
	menuBar			: Handle;
	window			: WindowPtr;
	ignoreError		: OSErr;
	total, contig	: LongInt;
	ignoreResult	: BOOLEAN;
	event			: EventRecord;
	count			: INTEGER;
	fi				: FailInfo;

	PROCEDURE HandleErr(error: INTEGER; message: LongInt);
	BEGIN
		IF error > 0 THEN
			AlertUser(0, error)
		ELSE
			AlertUser(error, message);
		ExitToShell;
	END; {HandleErr}

BEGIN
	gHasWaitNextEvent := TrapAvailable(_WaitNextEvent, ToolTrap);
	gInBackground := FALSE;

	InitGraf(@thePort);
	InitFonts;
	InitWindows;
	InitMenus;
	TEInit;
	InitDialogs(NIL);
	InitCursor;

	FOR count := 1 TO 3 DO
		ignoreResult := GetNextEvent(everyEvent, event);

	CatchFailures(fi, HandleErr);

	FailOSErrMsg(MPPOpen, eAppleTalk);
	FailOSErrMsg(ATPLoad, eAppleTalk);

	ignoreError := SysEnvirons(kSysEnvironsVersion, gMac);
	IF gMac.machineType < 0 THEN
		Failure(0, eWrongMachine);
	
	IF ORD(GetApplLimit) - ORD(ApplicZone) < kMinHeap THEN
		Failure(0, eSmallSize);

	PurgeSpace(total, contig);
	IF total < kMinSpace THEN
		Failure(0, eNoMemory);

	menuBar := GetNewMBar(rMenuBar);		{read menus into menu bar}
	FailNILMsg(menuBar, eNoMemory);
	SetMenuBar(menuBar);					{install menus}
	DisposHandle(menuBar);
	AddResMenu(GetMHandle(mApple), 'DRVR');	{add DA names to Apple menu}
	DrawMenuBar;
	
	Success(fi);
END; {Initialize}


{$S Main}
PROCEDURE Terminate;
VAR
	aWindow	: WindowPtr;
	closed	: BOOLEAN;

BEGIN
	closed := TRUE;
	REPEAT
		aWindow := FrontWindow;					{get the current front window}
		IF aWindow <> NIL THEN
			closed := DoCloseWindow(aWindow);	{close this window}
	UNTIL (NOT closed) | (aWindow = NIL);		{do all windows}
	IF closed THEN
		ExitToShell;							{exit if no cancellation}
END; {Terminate}


{$S Main}
PROCEDURE AdjustMenus;
VAR
	window			: WindowPtr;
	menu			: MenuHandle;

BEGIN
	window := FrontWindow;

	menu := GetMHandle(mFile);
	IF IsDAWindow(window) THEN				{we can allow desk accessories to be closed from the menu}
		EnableItem(menu, iClose)
	ELSE
		DisableItem(menu, iClose);			{but not our traffic light window}

	menu := GetMHandle(mEdit);
	IF IsDAWindow(window) THEN BEGIN		{a desk accessory might need the edit menu}
		EnableItem(menu, iUndo);
		EnableItem(menu, iCut);
		EnableItem(menu, iCopy);
		EnableItem(menu, iPaste);
		EnableItem(menu, iClear);
	END ELSE BEGIN							{but we know we do not}
		DisableItem(menu, iUndo);
		DisableItem(menu, iCut);
		DisableItem(menu, iCopy);
		DisableItem(menu, iClear);
		DisableItem(menu, iPaste);
	END;

END; {AdjustMenus}


{$S Main}
PROCEDURE DoMenuCommand(menuResult: LONGINT);
VAR
	menuID			: INTEGER;		{the resource ID of the selected menu}
	menuItem		: INTEGER;		{the item number of the selected menu}
	itemHit			: INTEGER;
	daName			: Str255;
	daRefNum		: INTEGER;
	handledByDA		: BOOLEAN;
	ignore			: BOOLEAN;
	fi				: FailInfo;
	
	PROCEDURE HandleMenu(error: INTEGER; message: LongInt);
	BEGIN
		HiliteMenu(0);				{unhighlight what MenuSelect (or MenuKey) hilited}
	END;

BEGIN
	CatchFailures(fi, HandleMenu);
	menuID := HiWrd(menuResult);	{use built-ins (for efficiency)...}
	menuItem := LoWrd(menuResult);	{to get menu item number and menu number}
	CASE menuID OF
		mApple:
			CASE menuItem OF
				iAbout:				{bring up alert for About}
					itemHit := Alert(rAboutAlert, NIL);
				OTHERWISE BEGIN		{all non-About items in this menu are DAs}
					GetItem(GetMHandle(mApple), menuItem, daName);
					daRefNum := OpenDeskAcc(daName);
				END;
			END;
		mFile:
			CASE menuItem OF
				iNew:
					DoZoneList;
				iClose:
					ignore := DoCloseWindow(FrontWindow);
				iQuit:
					Terminate;
			END;
		mEdit:						{call SystemEdit for DA editing & MultiFinder}
			handledByDA := SystemEdit(menuItem-1);	{since we don't do any editing}
	END;
	Success(fi);
	HiliteMenu(0);					{cleanup}
END; {DoMenuCommand}


{$S Main}
PROCEDURE AdjustCursor(mouse: Point; region: RgnHandle);
BEGIN
END; {AdjustCursor}


{$S Main}
PROCEDURE DoEvent(event: EventRecord);
VAR
	part, err	: INTEGER;
	window		: WindowPtr;
	hit			: BOOLEAN;
	key			: CHAR;
	fi			: FailInfo;
	aPoint		: Point;
	
	PROCEDURE HandleErr(error: INTEGER; message: LongInt);
	BEGIN
		IF error > 0 THEN
			AlertUser(0, error)
		ELSE
			AlertUser(error, message);
		EXIT(DoEvent);
	END; {HandleErr}

BEGIN
	CatchFailures(fi, HandleErr);
		
	CASE event.what OF
		mouseDown: BEGIN
			part := FindWindow(event.where, window);
			CASE part OF
				inMenuBar: BEGIN			{process the menu command}
					AdjustMenus;
					DoMenuCommand(MenuSelect(event.where));
				END;
				inSysWindow:				{let the system handle the mouseDown}
					SystemClick(event, window);
				inContent:;
				inDrag:;
				inGrow:;
				inZoomIn, inZoomOut:;
			END;
		END;
		keyDown, autoKey: BEGIN				{check for menukey equivalents}
			key := CHR(BAnd(event.message, charCodeMask));
			IF BAnd(event.modifiers, cmdKey) <> 0 THEN	{Command key down}
				IF event.what = keyDown THEN BEGIN
					AdjustMenus;			{enable/disable/check menu items properly}
					DoMenuCommand(MenuKey(key));
				END;
		END;                                {call DoActivate with the window and...}
		activateEvt:;
		updateEvt:;
		{1.01 - It is not a bad idea to at least call DIBadMount in response
		 to a diskEvt, so that the user can format a floppy.}
		diskEvt:
			IF HiWrd(event.message) <> noErr THEN BEGIN
				SetPt(aPoint, kDILeft, kDITop);
				err := DIBadMount(aPoint, event.message);
			END;
		kOSEvent:
			CASE BAnd(BRotL(event.message, 8), 255) OF	{high byte of message}
				kSuspendResumeMessage: BEGIN
					gInBackground := BAnd(event.message, kResumeMask) = 0;
				END;
			END;
	END;
	
	Success(fi);
END; {DoEvent}


{$S Main}
PROCEDURE EventLoop;
VAR
	cursorRgn	: RgnHandle;
	gotEvent	: BOOLEAN;
	event		: EventRecord;

BEGIN
	cursorRgn := NewRgn;			{weÕll pass WNE an empty region the 1st time thru}

	REPEAT
		IF gHasWaitNextEvent THEN	{put us 'asleep' forever under MultiFinder}
			gotEvent := WaitNextEvent(everyEvent, event, MAXLONGINT, cursorRgn)
		ELSE BEGIN
			SystemTask;				{must be called if using GetNextEvent}
			gotEvent := GetNextEvent(everyEvent, event);
		END;
		IF gotEvent THEN BEGIN
			AdjustCursor(event.where, cursorRgn); {make sure we have the right cursor}
			DoEvent(event);
		END;
		AdjustCursor(event.where, cursorRgn);
	UNTIL FALSE;					{loop forever; we quit through an ExitToShell}
END; {EventLoop}


PROCEDURE _DataInit; EXTERNAL;


{$S Main}
BEGIN
	UnloadSeg(@_DataInit);		{note that _DataInit must not be in Main!}
	MaxApplZone;				{expand the heap so code segments load at the top}

	InitSignals;
	Initialize;					{initialize the program}
	UnloadSeg(@Initialize);		{note that Initialize must not be in Main!}

	EventLoop;					{call the main event loop}
END.
